using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Linq;

using ERPStore.Extensions;

using CloudSoft.Extensions;

namespace ERPStore.Text
{
	[AttributeUsage(AttributeTargets.Property)]
	public class CSVElementAttribute : Attribute
	{
		public readonly string Prefix;
		public readonly int RepeatCount = 1;

		public CSVElementAttribute()
			: this(null)
		{

		}

		public CSVElementAttribute(int repeatCount)
			: this(null, repeatCount)
		{

		}

		public CSVElementAttribute(string prefix)
			: this(prefix, 1)
		{
		}

		public CSVElementAttribute(string prefix, int repeatCount)
		{
			Prefix = prefix;
			RepeatCount = repeatCount;
		}
	}

	/// <summary>
	/// Attribut pour la description des colonnes pour la serialization CSV
	/// </summary>
	[AttributeUsage(AttributeTargets.Property)]
	public class CSVColumnAttribute : Attribute
	{
		public string Name;
		public int FieldIndex = 0;
		public string Format;

		public CSVColumnAttribute()
		{

		}
	}

	/// <summary>
	/// Classe pour la serialisation des classes d'import/export au format CSV
	/// </summary>
	/// <typeparam name="T"></typeparam>
    public static class CSVSerializer<T>
    {
		static BindingFlags options = BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.Public;
		static RegexOptions regOptions = ((RegexOptions.IgnorePatternWhitespace
								 | RegexOptions.Singleline)
								 | RegexOptions.IgnoreCase);

		/// <summary>
		/// Deserializes the specified input.
		/// </summary>
		/// <param name="input">The input.</param>
		/// <returns></returns>
        public static T Deserialize(string input)
        {
            return (T)Deserialize(typeof(T), input);
        }

		private static object Deserialize(Type t, string input)
		{
			object result = Activator.CreateInstance(t);
			PropertyInfo[] list = t.GetProperties(options);

			string separator = ",";
			Regex reg = new Regex("((?:[^\",]|(?:\"(?:\\{2}|\\\"|[^\"])*?\"))*)", regOptions);
			MatchCollection coll = reg.Matches(input);
			List<string> items = new List<string>();
			int loop = 0;
			foreach (Match m in coll)
			{
				if (loop % 2 == 0)
				{
					items.Add(m.Groups[0].Value.Trim('"').Trim(',').Trim('"').Trim());
				}
				loop++;
			}
			int pos = 0;
			CultureInfo ci = new CultureInfo("en-US");
			foreach (PropertyInfo pi in list)
			{
				object[] attrs = pi.GetCustomAttributes(typeof(CSVColumnAttribute), true);
				if (attrs != null && attrs.Length > 0)
				{
					string item = string.Empty;
					try
					{
						item = items[pos];
						pos++;
					}
					catch
					{
						throw;
					}
					object o = null;
					if (pi.PropertyType == typeof(DateTime))
					{
						int year = int.Parse(item.Substring(0, 4));
						int month = int.Parse(item.Substring(4, 2));
						int day = int.Parse(item.Substring(6, 2));
						o = new DateTime(year, month, day);
					}
					else
					{
						try
						{
							o = System.Convert.ChangeType(item, pi.PropertyType, ci);
						}
						catch (Exception)
						{
							throw;
						}
					}

					try
					{
						if (o is string)
						{
							o = ((string)o).Replace("$CRLF$", "\r\n");
						}
						pi.SetValue(result, o, null);
					}
					catch (Exception)
					{
						throw;
					}
				}
				attrs = pi.GetCustomAttributes(typeof(CSVElementAttribute), true);
				if (attrs != null && attrs.Length > 0)
				{
					CSVElementAttribute att = attrs[0] as CSVElementAttribute;
					// On recherche le nombre de properties
					if (!pi.PropertyType.IsGenericType)
					{
						int colCount = 0;
						string partOfInput = GetPartOfInput(pi.PropertyType, items,  pos, separator, out colCount);
						if (IsPartEmpty(separator, colCount, partOfInput))
						{
							continue;
						}
						object elm = Deserialize(pi.PropertyType, partOfInput);
						pi.SetValue(result, elm, null);
						pos += colCount;
					}
					else
					{
						// Type genType = pi.PropertyType.GetGenericTypeDefinition();
						object instance = Activator.CreateInstance(pi.PropertyType);
						Type[] args = pi.PropertyType.GetGenericArguments();
						for (int i = 0; i < att.RepeatCount; i++)
						{
							int colCount = 0;
							string partOfInput = GetPartOfInput(args[0], items, pos, separator, out colCount);
							if (IsPartEmpty(separator, colCount, partOfInput))
							{
								continue;
							}
							object elm = Deserialize(args[0], partOfInput);
							((System.Collections.IList)instance).Add(elm);
							pos += colCount;
						}
						pi.SetValue(result, instance, null);
					}
				}
			}

			return result;
		}


		/// <summary>
		/// Serializes the specified input.
		/// </summary>
		/// <param name="input">The input.</param>
		/// <returns></returns>
        public static string Serialize(T input, string separator = ";", string cultureInfo = "en-US")
        {
			var list = from pi in input.GetType().GetProperties(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.Public)
					   let attrs = pi.GetCustomAttributes(typeof(CSVColumnAttribute), true)
					   where attrs != null && attrs.Length == 1
					   let att = attrs[0] as CSVColumnAttribute
					   select new { PropertyInfo = pi, CSVColumn = att };

            var ci = new System.Globalization.CultureInfo(cultureInfo);

            var result = new List<string>();

            foreach (var item in list.OrderBy(i => i.CSVColumn.FieldIndex))
            {
				object value = item.PropertyInfo.GetValue(input, null);
                string s = string.Empty;
				if (item.PropertyInfo.PropertyType == typeof(string))
                {
					if (value == null || (string)value == "")
					{
						s = "\"\"";
					}
					else
					{
						s = "\"" + value.ToString().Replace("\"", "\"\"") + "\"";
					}
                }
				else if (item.PropertyInfo.PropertyType == typeof(DateTime))
	            {
					var f = item.CSVColumn.Format ?? "{0:yyyyMMdd}";
		            s = "\"" + string.Format(f, value) + "\"";
	            }
				else if (item.PropertyInfo.PropertyType == typeof(decimal))
                {
					var f = item.CSVColumn.Format ?? "{0:f4}";
                    s = string.Format(ci,f, value);
                }
				else if (item.PropertyInfo.PropertyType == typeof(double))
                {
					var f = item.CSVColumn.Format ?? "{0:f2}";
					s = string.Format(ci, f, value);
                }
                else
                {
                    s = value.ToString();
                }

				result.Add(s);
            }
            return result.JoinString(separator);
        }

		/// <summary>
		/// Gets the header.
		/// </summary>
		/// <returns></returns>
        public static string GetHeader()
        {
			return GetHeader(typeof(T));
        }

		/// <summary>
		/// Gets the header.
		/// </summary>
		/// <param name="t">The t.</param>
		/// <returns></returns>
		private static string GetHeader(Type t)
		{
			return GetHeader(t, null);
		}

		/// <summary>
		/// Gets the header.
		/// </summary>
		/// <param name="t">The t.</param>
		/// <param name="prefix">The prefix.</param>
		/// <returns></returns>
		private static string GetHeader(Type t, string prefix)
		{
			return GetHeader(t, prefix, 0);
		}


		/// <summary>
		/// Gets the header.
		/// </summary>
		/// <param name="t">The t.</param>
		/// <param name="prefix">The prefix.</param>
		/// <param name="loop">The loop.</param>
		/// <returns></returns>
		private static string GetHeader(Type t, string prefix, int loop)
		{
			PropertyInfo[] list = t.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);

			string result = null;
			string separator = ",";
			if (loop > 0 && prefix != null)
			{
				prefix = prefix + loop.ToString();
			}
			foreach (PropertyInfo pi in list)
			{
				object[] attrs = pi.GetCustomAttributes(typeof(CSVColumnAttribute), true);
				string displayName = string.Empty;
				if (attrs != null && attrs.Length > 0)
				{
					CSVColumnAttribute attribute = attrs[0] as CSVColumnAttribute;
					if (attribute.Name == null)
					{
						displayName = pi.Name;
					}
					else
					{
						displayName = attribute.Name;
					}
					displayName = prefix + displayName;
					if (pi.PropertyType == typeof(string)
						|| pi.PropertyType == typeof(DateTime))
					{
						displayName = "\"" + displayName.Replace("\"", "\"\"") + "\"";
					}
					result = result + displayName + separator;
					continue;
				}

				// On recherche les types complexes
				attrs = pi.GetCustomAttributes(typeof(CSVElementAttribute), true);
				if (attrs != null && attrs.Length > 0)
				{
					CSVElementAttribute att = attrs[0] as CSVElementAttribute;
					string elmPrefix;
					if (att.Prefix == null)
					{
						elmPrefix = pi.Name;
					}
					else
					{
						elmPrefix = att.Prefix;
					}
					string elementHeader = null;
					if (pi.PropertyType.IsGenericType)
					{
						Type[] args = pi.PropertyType.GetGenericArguments();
						for (int i = 0; i < att.RepeatCount; i++)
						{
							loop = loop + 1;
							elementHeader += GetHeader(args[0], elmPrefix, loop);
							elementHeader += (i != att.RepeatCount - 1) ? "," : "";
						}
					}
					else
					{
						elementHeader = GetHeader(pi.PropertyType, elmPrefix, loop); 
					}
					result = result + elementHeader + separator;
				}
			}

			if (result.EndsWith(","))
			{
				result = result.Substring(0, result.Length - 1);
			}

			return result;
		}

		#region Helpers

		private static bool IsPartEmpty(string separator, int count, string input)
		{
			string emptyPart = string.Empty;
			for (int i = 0; i < count - 1; i++)
			{
				emptyPart += "\"" + "\"" + separator ;
			}
			emptyPart += "\"" + "\"";
			if (input == emptyPart)
			{
				return true;
			}
			return false;
		}

		private static string GetPartOfInput(Type t, List<string> items, int pos, string separator, out int columnCount)
		{
			columnCount = CountOfCSVColumn(t);
			string partOfInput = null;
			for (int i = pos; i < pos + columnCount; i++)
			{
				partOfInput += "\"" + items[i] + "\"";

				if (i != pos + columnCount - 1)
				{
					partOfInput += separator;
				}
			}
			return partOfInput;
		}

		private static int CountOfCSVColumn(Type t)
		{
			int result = 0;
			// On recherche le nombre de properties
			PropertyInfo[] piList = t.GetProperties(options);
			foreach (PropertyInfo pi in piList)
			{
				object[] attrs = pi.GetCustomAttributes(typeof(CSVColumnAttribute), true);
				if (attrs != null && attrs.Length > 0)
				{
					result++;
				}
			}
			return result;
		}

		#endregion
	}
}
